package com.dirox.confluence.labelmanagement.service.imp;

import com.atlassian.confluence.core.ConfluenceEntityObject;
import com.atlassian.confluence.core.service.ValidationError;
import com.atlassian.confluence.labels.Label;
import com.atlassian.confluence.labels.LabelManager;
import com.atlassian.confluence.labels.Labelable;
import com.atlassian.confluence.labels.Namespace;
import com.atlassian.confluence.labels.service.AddLabelsCommand;
import com.atlassian.confluence.labels.service.LabelsService;
import com.atlassian.confluence.labels.service.RemoveLabelCommand;
import com.atlassian.confluence.labels.service.ValidateLabelsCommand;
import com.atlassian.confluence.pages.Attachment;
import com.atlassian.confluence.pages.BlogPost;
import com.atlassian.confluence.pages.Page;
import com.atlassian.confluence.pages.PageManager;
import com.atlassian.confluence.security.Permission;
import com.atlassian.confluence.security.PermissionManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;

import com.atlassian.user.User;

import com.dirox.confluence.labelmanagement.service.LabelManagementService;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LabelManagementServiceImp implements LabelManagementService {
    private SpaceManager spaceManager;
    private LabelManager labelManager;
    private LabelsService labelsService;
    private PermissionManager permissionManager;
    private PageManager pageManager;

    /**
     * <p>getLabelCountInSpace method:<br/>
     * Used to count number of labels
     * which current users have EDIT permission on.<br/>
     * Affected on a specific space.</p>
     *
     * @param spaceKey
     *
     * @return map of Label objects and number of that labels
     *
     * @see LabelManagementService#getLabelsCountInSpace(String spaceKey)
     */
    @Override
    public Map<Label, Integer> getLabelsCountInSpace(String spaceKey) {
        List<Space> spaceList = new ArrayList<Space>();
        Space space = spaceManager.getSpace(spaceKey);

        if (space != null) {
            spaceList.add(space);
        }

        return getLabelsCount(spaceList);
    }

    /**
     * <p>getLabelCountInAllSpaces method:<br/>
     * Used to count number of labels
     * which the current user have EDIT permission on.<br/>
     * Affect all spaces.</p>
     *
     * @return map of Label objects and number of that labels
     *
     * @see LabelManagementService#getLabelsCountInAllSpaces()
     */
    @Override
    public Map<Label, Integer> getLabelsCountInAllSpaces() {
        return getLabelsCount(getSpaces());
    }

    /**
     * <p>renameLabeInAllSpace method:<br/>
     * Used to rename labels from all spaces.
     * Take old-label, new-label then rename old-label to new-label.<br/>
     * Affect all spaces.</p>
     *
     * @param fromLabelName
     * @param toLabelName
     *
     * @return number of affected entity contents.
     *
     * @see LabelManagementService#renameLabelInAllSpaces(String fromLabelName,
     *      String toLabelName)
     */
    @Override
    public long renameLabelInAllSpaces(String fromLabelName, String toLabelName) {
        return mergeLabelInAllSpaces(fromLabelName, toLabelName);
    }

    /**
     * <p>renameLabeInSpace method: used to rename labels in space.
     * Take spaceKey, old-label, new-label then rename old-label to new-label.
     * Affect only on that space.</p>
     *
     * @param SpaceKey
     * @param fromLabelName
     * @param toLabelName
     *
     * @return number of affected entity contents.
     *
     * @see LabelManagementService#renameLabelInSpace(String spaceKey,String
     *      fromLabelName, String toLabelName)
     */
    @Override
    public long renameLabelInSpace(String spaceKey, String fromLabelName,
        String toLabelName) {
        return mergeLabelInSpace(spaceKey, fromLabelName, toLabelName);
    }

    /**
     * <p>mergeLabelInSpace method: used to merge label in all spaces.<br/>
     * Take first and second label, then find those in all spaces
     * and merge all labels with those name.</p>
     *
     * @param fromLabelName
     * @param toLabelName
     *
     * @return number of affected contents.
     * @see LabelManagementService#renameLabelInAllSpaces(String fromLabelName,
     *      String toLabelName)
     */
    @Override
    public long mergeLabelInAllSpaces(String fromLabelName, String toLabelName) {
        return mergeLabels(getSpaces(), fromLabelName, toLabelName);
    }

    /**
     * <p>mergeLabelInSpace method: used to merge label in space.<br/>
     * Take spaceKey, then find and merge all labels in that space.</p>
     *
     * @param spaceKey
     * @param fromLabelName
     * @param toLabelName
     *
     * @return affected entity contents.
     *
     * @see LabelManagementService#mergeLabelInSpace(String spaceKey,String
     *      fromLabelName, String toLabelName)
     */
    @Override
    public long mergeLabelInSpace(String spaceKey, String fromLabelName,
        String toLabelName) {
        List<Space> spaceList = new ArrayList<Space>();
        Space space = spaceManager.getSpace(spaceKey);

        if (space != null) {
            spaceList.add(space);
        }

        return mergeLabels(spaceList, fromLabelName, toLabelName);
    }

    protected long mergeLabels(List<Space> spaceList, String fromLabelName,
        String toLabelName) {
        long result = 0;
        User user = getCurrentUser();
        Label fromLabelObj = labelManager.getLabel(fromLabelName);

        if (fromLabelObj == null) {
            return result;
        }

        for (Space space : spaceList) {
            for (Page page : getPagesInSpace(space)) {
                if (page.getLabels().contains(fromLabelObj) &&
                        mergeLabel(user, page, fromLabelObj.getId(), toLabelName)) {
                    result++;
                }

                for (Attachment attachment : page.getAttachments()) {
                    if (attachment.getLabels().contains(fromLabelObj) &&
                            mergeLabel(user, attachment, fromLabelObj.getId(),
                                toLabelName)) {
                        result++;
                    }
                }
            }

            for (BlogPost blogPost : getBlogPostsInSpace(space)) {
                if (blogPost.getLabels().contains(fromLabelObj) &&
                        mergeLabel(user, blogPost, fromLabelObj.getId(),
                            toLabelName)) {
                    result++;
                }

                for (Attachment attachment : blogPost.getAttachments()) {
                    if ((fromLabelObj != null) &&
                            attachment.getLabels().contains(fromLabelObj) &&
                            mergeLabel(user, attachment, fromLabelObj.getId(),
                                toLabelName)) {
                        result++;
                    }
                }
            }
        }

        return result;
    }

    protected Map<Label, Integer> getLabelsCount(List<Space> spaceList) {
        User user = getCurrentUser();
        Map<Label, Integer> results = new HashMap<Label, Integer>();
        Integer currentValue;

        for (Space space : spaceList) {
            // page
            for (Page page : getPagesInSpace(space)) {
                for (Label label : page.getLabels()) {
                    if (hasEditPermission(label, user, page)) {
                        currentValue = results.get(label);
                        currentValue = (currentValue == null) ? 0 : currentValue;
                        results.put(label, (currentValue + 1));
                    }
                }

                // count with page attachment
                for (Attachment attachment : page.getAttachments()) {
                    for (Label label : attachment.getLabels()) {
                        if (hasEditPermission(label, user, attachment)) {
                            currentValue = results.get(label);
                            currentValue = (currentValue == null) ? 0
                                                                  : currentValue;
                            results.put(label, (currentValue + 1));
                        }
                    }
                }
            }

            // blogPost
            for (BlogPost blogPost : getBlogPostsInSpace(space)) {
                for (Label label : blogPost.getLabels()) {
                    if (hasEditPermission(label, user, blogPost)) {
                        currentValue = results.get(label);
                        currentValue = (currentValue == null) ? 0 : currentValue;
                        results.put(label, (currentValue + 1));
                    }
                }

                // count with blogPost attachment
                for (Attachment attachment : blogPost.getAttachments()) {
                    for (Label label : attachment.getLabels()) {
                        if (hasEditPermission(label, user, attachment)) {
                            currentValue = results.get(label);
                            currentValue = (currentValue == null) ? 0
                                                                  : currentValue;
                            results.put(label, (currentValue + 1));
                        }
                    }
                }
            }
        }

        return results;
    }

    protected boolean mergeLabel(User user, Labelable labelable, long labelId,
        String toLabelName) {
        RemoveLabelCommand removeCommand = null;
        AddLabelsCommand addCommand = null;
        Label toLabelObj = labelManager.getLabel(toLabelName);

        if ((toLabelObj == null) ||
                !labelable.getLabels().contains(toLabelObj)) {
            addCommand = labelsService.newAddLabelCommand(toLabelName, user,
                    ((ConfluenceEntityObject) labelable).getId(),
                    getEntityType(labelable));
        }

        removeCommand = labelsService.newRemoveLabelCommand(labelId, user,
                ((ConfluenceEntityObject) labelable).getId());

        if (addCommand == null) {
            if (removeCommand.isValid()) {
                removeCommand.execute();
            } else {
                return false;
            }
        } else if (addCommand.isValid() && removeCommand.isValid()) {
            addCommand.execute();
            removeCommand.execute();
        } else {
            return false;
        }

        return true;
    }

    /**
     * <p>deleteLabelInAllSpace() method:<br/>
     * Used to deleting all labels with name input in all spaces.</p>
     *
     * @param labelName
     * @return number of affected contents
     *
     * @see LabelManagementService#deleteLabelInAllSpaces(String)
     */
    @Override
    public long deleteLabelInAllSpaces(String labelName) {
        return deleteLabels(getSpaces(), labelName);
    }

    /**
     * <p>deleteLabelInSpace() method:<br/>
     * Used to deleting all labels with name input in specific space.</p>
     *
     * @param spaceKey
     * @param labelName
     *
     * @return number of affected contents
     *
     * @see LabelManagementService#deleteLabelInSpace(String,String)
     */
    @Override
    public long deleteLabelInSpace(String spaceKey, String labelName) {
        List<Space> spaceList = new ArrayList<Space>();
        Space space = spaceManager.getSpace(spaceKey);

        if (space != null) {
            spaceList.add(space);
        }

        return deleteLabels(spaceList, labelName);
    }

    protected long deleteLabels(List<Space> spaceList, String labelName) {
        long result = 0;
        User user = getCurrentUser();
        Label labelObj = labelManager.getLabel(labelName);

        if (labelObj == null) {
            return result;
        }

        for (Space space : spaceList) {
            //			remove label in pages of space
            for (Page page : getPagesInSpace(space)) {
                if (page.getLabels().contains(labelObj) &&
                        removeLabel(labelObj.getId(), user, page)) {
                    result++;
                }

                //				remove label in attachments of page
                for (Attachment attachment : page.getAttachments()) {
                    if (attachment.getLabels().contains(labelObj) &&
                            removeLabel(labelObj.getId(), user, attachment)) {
                        result++;
                    }
                }
            }

            //			remove label in blogPosts of space
            for (BlogPost blogPost : getBlogPostsInSpace(space)) {
                if (blogPost.getLabels().contains(labelObj) &&
                        removeLabel(labelObj.getId(), user, blogPost)) {
                    result++;
                }

                //				remove label in attachments of BlogPost
                for (Attachment attachment : blogPost.getAttachments()) {
                    if (attachment.getLabels().contains(labelObj) &&
                            removeLabel(labelObj.getId(), user, attachment)) {
                        result++;
                    }
                }
            }
        }

        return result;
    }

    /**
     * Remove label for Labelable with user inputed
     * <p>
     *
     * @param labelName
     *            - String name of label
     * @param User
     *            object - Current user
     * @param Labelable
     *            object - Target object to remove label
     * @return <code>true</code> if already removed, <code>false</code> if not
     *         able to remove
     *
     */
    protected boolean removeLabel(long labelId, User user, Labelable labelable) {
        RemoveLabelCommand removeCommand = labelsService.newRemoveLabelCommand(labelId,
                user, ((ConfluenceEntityObject) labelable).getId());

        if (removeCommand.isValid()) {
            removeCommand.execute();

            return true;
        } else {
            return false;
        }
    }

    /**
     * Check user edit permission for object
     *
     * @param Label object
     * @param User  object- Current user
     * @param Object
     *
     * @return <code>true</code> if has permission. <code>false</code> if
     *         doesn't have permission.
     */
    protected boolean hasEditPermission(Label label, User user, Object object) {
        if ((label != null) && (object != null) && Namespace.isGlobal(label) &&
                permissionManager.hasPermission(user, Permission.EDIT, object)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Get current login user
     *
     * @return User object - current user
     */
    protected User getCurrentUser() {
        return AuthenticatedUserThreadLocal.getUser();
    }

    /**
     * Get All Spaces
     *
     * @return List of Spaces
     */
    protected List<Space> getSpaces() {
        return spaceManager.getAllSpaces();
    }

    /**
     * Get All Spaces
     *
     * @return List of Spaces
     */
    protected List<Space> getSpacesWithLabel(String label) {
        return spaceManager.getAllSpaces();
    }

    /**
     * Get All current BlogPosts in Space
     *
     * @param Space
     *            object - target space
     * @return List of BlogPosts
     */
    protected List<BlogPost> getBlogPostsInSpace(Space space) {
        return pageManager.getBlogPosts(space, true);
    }

    /**
     * Get All current Pages in Space
     *
     * @param Space
     *            object - target space
     * @return List of Pages
     */
    protected List<Page> getPagesInSpace(Space space) {
        return pageManager.getPages(space, true);
    }

    /**
     * Get Entity Type of Object
     *
     * @param Object
     *
     * @return String value - type of Object
     *                         Null - object is not instanceOf (Space, Page, Attachment, BlogPost)
     */
    protected String getEntityType(Object object) {
        if (object instanceof Space) {
            return "Space";
        } else if (object instanceof Page) {
            return "Page";
        } else if (object instanceof Attachment) {
            return "Attachment";
        } else if (object instanceof BlogPost) {
            return "BlogPost";
        } else {
            return null;
        }
    }

    /**
     * @see LabelManagementService#getLabelValidationError(String)
     */
    public ValidationError getLabelValidationError(String labelName) {
        ValidateLabelsCommand command = labelsService.newValidateLabelCommand(labelName,
                getCurrentUser());

        if (!command.isValid()) {
            return command.getValidationErrors().iterator().next();
        } else {
            return null;
        }
    }

    public void setSpaceManager(SpaceManager spaceManager) {
        this.spaceManager = checkNotNull(spaceManager);
    }

    public void setLabelManager(LabelManager labelManager) {
        this.labelManager = checkNotNull(labelManager);
    }

    public void setLabelsService(LabelsService labelsService) {
        this.labelsService = checkNotNull(labelsService);
    }

    public void setPermissionManager(PermissionManager permissionManager) {
        this.permissionManager = checkNotNull(permissionManager);
    }

    public void setPageManager(PageManager pageManager) {
        this.pageManager = checkNotNull(pageManager);
    }

    @Override
    public boolean isExistLabel(String labelName) {
        return labelManager.getLabel(labelName) != null;
    }

    @Override
    public boolean isExistLabelInSpace(String spaceKey, String labelName) {
        Label label = labelManager.getLabel(labelName);

        return ((label != null) &&
        labelManager.getLabelsInSpace(spaceKey).contains(label));
    }
}
